#include "builder.h"

#include <fstream>
#include <sstream>

#include "util/strutil.h"
#include "util/logger/logger.h"
#include "util/collector/for.h"

#include "cutter.h"


namespace
{

class IncludeScope
{
public:
    IncludeScope(std::vector<fs::path> &levels, const fs::path &path)
        : levels_(levels), path_(path) {
        levels_.push_back(path);
    }

    ~IncludeScope() {
        levels_.pop_back();
    }

private:
    std::vector<fs::path> &levels_;
    const fs::path &path_;
};

} // namespace anonymous


static std::string makeTitle(const fs::path &path) {
    std::ostringstream ss;
    ss << "html::tmplt::Builder(" << path.generic_string() << ") ";
    return ss.str();
}

namespace html::tmplt
{

Builder::Builder(const fs::path &path)
    : title_(makeTitle(path))
    , path_(fs::absolute(path).lexically_normal()) {

}

void Builder::operator()(Parts &result, std::set<fs::path> &deps) {
    std::vector<fs::path> includeLevels;
    result = buildFileRecursive(path_, includeLevels);
    deps = std::move(deps_);
}

Builder::Parts Builder::buildFileRecursive(const fs::path &path, std::vector<fs::path> &includeLevels) {
    deps_.insert(path);
    IncludeScope includeScope(includeLevels, path);
    Parts result = cut(path);
    Iter iter = result.begin();
    while (iter != result.end()) {
        iter = handlePart(path, result, iter, includeLevels);
    }
    return result;
}

Builder::Iter Builder::handlePart(const fs::path &path, Parts &parts, Iter current, std::vector<fs::path> &includeLevels) {
    namespace s = strutil;
    if (current->isText()) {
        return ++current;
    }
    const std::string &content = current->content();
    if (s::startWith(content, "include:")) {
        return handleIncludeCmd(path, parts, current, std::string(s::split(content, ':')[1]), includeLevels);
    }
    return ++current;
}

static fs::path rawStrToPath(const fs::path &path, const std::string &raw) {
    const fs::path rawPath(raw);
    if (rawPath.is_absolute()) {
        return rawPath;
    } else {
        return fs::absolute(path.parent_path() / rawPath).lexically_normal();
    }
}

template <typename E>
static bool contains(const std::vector<E> &c, const E &e) {
    for (const E &elem : c) {
        if (elem == e) {
            return true;
        }
    }
    return false;
}

Builder::Iter Builder::handleIncludeCmd(const fs::path &path, Parts &parts, Iter current, const std::string &rawIncludedPath, std::vector<fs::path> &includeLevels) {
    Builder::Iter next = parts.erase(current);
    const fs::path includedPath = rawStrToPath(path, rawIncludedPath);
    if (contains(includeLevels, includedPath)) {
        LOGE << title() << "loop include detected: '" << includedPath << "'";
        return next;
    }
    if (!fs::exists(includedPath) || !fs::is_regular_file(includedPath)) {
        LOGE << title() << "bad include: '" << includedPath << "' from html template '" << path << "'";
        return next;
    }
    Parts includeParts = buildFileRecursive(includedPath, includeLevels);
    for (Part &includePart : includeParts) {
        next = parts.insert(next, std::move(includePart));
        ++next;
    }
    return next;
}

Builder::Parts Builder::cut(const fs::path &path) {
    Parts result;
    CollectorFor<decltype(result)> collector(result);
    Cutter cutter(collector);
    std::ifstream in(path.generic_string());
    char c = 0;
    while (in.get(c)) {
        cutter.feed(c);
    }
    cutter.stop();
    return result;
}

} // namespace html::tmplt
